using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.Drawing.Imaging;

namespace OsdmSoftware.PocketPicture
{
    public interface IBrush
    {
        void StartPainting();
        void EndPainting();
        void ClearStatus();

        void DrawEllipse(int x1, int y1, int x2, int y2);
        void DrawLine(int x1, int y1, int x2, int y2);
        void DrawRectangle(int x1, int y1, int x2, int y2);
        void DrawFromMouse(int x1, int y1, int x2, int y2);
        void FillEllipse(int x1, int y1, int x2, int y2);
        void FillRectangle(int x1, int y1, int x2, int y2);
    }

    public abstract class BaseBrush: IBrush
    {
        PocketPictureForm form;

        protected Point lastMouseXY;

        /* Three fields to describe segment border that is perpendicular to axis (axis is connecting mouse movements) */
        private double lastPerpFactor; // x(y) = lastMouseXY.X + (y - lastMouseXY.Y) * lastPerpFactor
        private bool isLastPerpFactorInfinite; // factor is infinite is border is horizontal
        private bool outerForLastPerpIsOnTheLeft; // for horizontal border left == up, right == down

        // For first segment we need to draw opening hemisphere
        private bool isFirst;

        // Target bitmap
        private BitmapData bmpData;

        // Source bitmap (that is, bitmap without current brush drawing)
        protected BitmapData sourceBmpData;

        protected int bitmapWidth;
        protected int bitmapHeight;
        private int oldBresenhamY;

        protected int currX, currY; // coordinates of current mouse movement
        protected double l; // segment length
        protected double dx; // lastMouseXY.X - currX
        protected double dy; // lastMouseXY.Y - currY
        protected double xfactor;
        protected int intL100; // segment length
        protected int intDX; // lastMouseXY.X - currX
        protected int intDY; // lastMouseXY.Y - currY
        protected ColorBgra selectedColor; // cached selected color
        protected byte[,] coefs; // array of bytes equal to bitmap in size, you can use it for storing transparency info 

        /* Class describing circle sector drawing info*/
        private class HemisphereInfo
        {
            public bool drawHemisphere;
            public bool isLeftSideUnlimited;
            public bool isRightSideUnlimited;
            public double leftSideLineFactor;
            public double rightSideLineFactor;
        }

        /* 
         * Low hemisphere means hemisphere with lesser y. On the screen it is high hemisphere.
         * Vice versa for high hemisphere.
         */
        private HemisphereInfo lowHemisphere = new HemisphereInfo();
        private HemisphereInfo highHemisphere = new HemisphereInfo();
        public PocketPictureForm Form { get { return form; } set { form = value; } }

        public void ClearStatus()
        {
            coefs = null;
        }

        public void StartPainting()
        {
            isFirst = true;

            if (coefs == null)
                coefs = new byte[Form.BitmapZoomer.Bitmap.Width, Form.BitmapZoomer.Bitmap.Height];
        }

        public void EndPainting()
        {
            //DrawSegment(lastMouseXY.X, lastMouseXY.Y, true);
        }

        protected virtual void ComputeAdditionalData()
        {
        }

        public void DoDrawLine()
        {
            Bitmap bmp = Form.BitmapZoomer.Bitmap;
            /*
             * Comments to formulae:
             * We are drawing a rectangle with axis on a line lastMouseXY -> (x,y) and width equal to Form.PenWidth
             * Lets take triangle which is formed by axis and some point on the rectangle border line that is parallel to axis (RBP)
             * 
             *                        (x, y) 
             *                   *   /
             *                  *   @
             *                 *   *   *
             *        RBP(1)--@   s   @--RBP(2)
             *               *   i   *
             *              *   x   *
             *             *   a   *
             *            *   *   *
             *               @   *
             *             /    *
             * lastMouseXY
             * 
             * Triangle area (S) = base * altitude / 2
             * in our case base = l, altitude = Form.PenWidth / 2, so
             * S = l * Form.PenWidth / 4
             * 
             * On the other hand, an area of the triangle formed by two vectors (x1, y1) - (x2, y2) is equal to
             * abs(x2*y1)/2 - abs(x1*y2)/2
             * 
             * Lets take RBP as point of origin, then x1 = (x - RBP.X), y1 = (y - RBP.Y), x2 = (lastMouseXY.X - RBP.X), y2 = (lastMouseXY.Y - RBP.Y)
             * So we have an equation:
             * l * Form.PenWidth / 4 = abs((lastMouseXY.X - RBP.X)*(y - RBP.Y))/2 - abs((x - RBP.X)*(lastMouseXY.Y - RBP.Y))/2
             * 
             * from which we can deduce that
             * 
             * RBP.X = ((+ or -)Form.PenWidth / 2 * l  - x * lastMouseXY.Y + lastMouseXY.X * y - RBP.Y * (lastMouseXY.X - x)) / (y - lastMouseXY.Y)
             * 
             * Now if we take any Y coordinate we can calculate two RBP of rectangle
             * 
             * But now lets not forget about border lines that are perpendicular to axis.
             * Fortunately, we can just use line equation for them: x = add + factor * y
             * factor for axis is (lastMouseXY.X - x) / (lastMouseXY.Y - x)
             * factor for perpendicular line is equal to 1/(factor for axis), that is (lastMouseXY.Y - x)/(lastMouseXY.X - x)
             * Knowing that border line must pass (x, y) or lastMouseXY it is easy to compute "add" component
             * 
             */

            // Segment length
            l = Math.Sqrt((double) (currY - lastMouseXY.Y) * (currY - lastMouseXY.Y) + (double) (currX - lastMouseXY.X) * (currX - lastMouseXY.X));
            intL100 = (int) (l * 100 + 0.5);
            dx = lastMouseXY.X - currX;
            intDX = lastMouseXY.X - currX;
            dy = lastMouseXY.Y - currY;
            intDY = lastMouseXY.Y - currY;
            double pt1 = 0, pt2 = 0;

            if (lastMouseXY.Y != currY)
            {
                pt1 = Form.PenWidth * l / 2D - currX * lastMouseXY.Y + lastMouseXY.X * currY;
                pt2 = -Form.PenWidth * l / 2D - currX * lastMouseXY.Y + lastMouseXY.X * currY;
                if (-pt1 / dy > -pt2 / dy)
                {
                    double tmp;
                    tmp = pt1;
                    pt1 = pt2;
                    pt2 = tmp;
                }
            }

            double xminadd, xmaxadd;

            int y1, y2;

            if (lastMouseXY.X == currX)
            {
                if (currY < lastMouseXY.Y)
                {
                    y1 = currY;
                    y2 = lastMouseXY.Y;
                }
                else
                {
                    y1 = lastMouseXY.Y;
                    y2 = currY;
                }

                xfactor = 0;
                xminadd = 0;
                xmaxadd = bmp.Width;
            }
            else
            {
                if (currY < lastMouseXY.Y)
                {
                    y1 = currY - Form.PenWidth / 2;
                    y2 = lastMouseXY.Y + Form.PenWidth / 2;
                }
                else
                {
                    y1 = lastMouseXY.Y - Form.PenWidth / 2;
                    y2 = currY + Form.PenWidth / 2;
                }

                xfactor = dy / dx;

                if (lastMouseXY.X > currX)
                {
                    xminadd = currX + currY * xfactor;
                    xmaxadd = lastMouseXY.X + lastMouseXY.Y * xfactor;
                }
                else
                {
                    xmaxadd = currX + currY * xfactor;
                    xminadd = lastMouseXY.X + lastMouseXY.Y * xfactor;
                }

                xfactor = -xfactor;
            }

            if (y1 < 0)
                y1 = 0;

            if (y2 >= bmp.Height)
                y2 = bmp.Height - 1;

            ComputeAdditionalData();

            unsafe
            {
                byte* pSourceRow = (byte*) sourceBmpData.Scan0.ToPointer();
                byte* pTargetRow = (byte*) bmpData.Scan0.ToPointer();
                pSourceRow += sourceBmpData.Stride * y1;
                pTargetRow += bmpData.Stride * y1;

                for (int yn = y1; yn <= y2; yn++)
                {
                    int xs, xe;
                    if (lastMouseXY.Y != currY)
                    {
                        xs = (int) Math.Ceiling((pt1 - yn * dx) / -dy);
                        xe = (int) Math.Floor((pt2 - yn * dx) / -dy);
                    }
                    else
                    {
                        xs = 0;
                        xe = bmp.Width - 1;
                    }

                    xs = Math.Max(xs, (int) Math.Ceiling(xminadd + xfactor * yn));
                    xe = Math.Min(xe, (int) Math.Floor(xmaxadd + xfactor * yn));

                    if (xs < 0)
                        xs = 0;
                    else if (xs >= Form.BitmapZoomer.Bitmap.Width)
                        xs = Form.BitmapZoomer.Bitmap.Width - 1;
                    if (xe < 0)
                        xe = 0;
                    else if (xe >= Form.BitmapZoomer.Bitmap.Width)
                        xe = Form.BitmapZoomer.Bitmap.Width - 1;

                    DrawHorizLine(pSourceRow, pTargetRow, xs, xe, yn, false);

                    pSourceRow += sourceBmpData.Stride;
                    pTargetRow += bmpData.Stride;
                }
            }
        }

        protected abstract unsafe void DrawHorizLine(byte* pSourceRow, byte* pTargetRow, int xs, int xe, int yn, bool circle);

        public void DrawEllipse(int x1, int y1, int x2, int y2)
        {
            throw new Exception("The method or operation is not implemented.");
        }

        public void DrawLine(int x1, int y1, int x2, int y2)
        {
            if (x1 == x2 && y1 == y2)
            {
                return;
            }

            lastMouseXY.X = x1;
            lastMouseXY.Y = y1;
            currX = x2;
            currY = y2;
            Bitmap bmp = Form.BitmapZoomer.Bitmap;
            Rectangle rect1 = new Rectangle(0, 0, bmp.Width, bmp.Height);
            bmpData = bmp.LockBits(rect1, ImageLockMode.ReadWrite, ColorBgra.PixelFormat);
            sourceBmpData = Form.LastModifications.LockBits(rect1, ImageLockMode.ReadWrite, ColorBgra.PixelFormat);
            bitmapHeight = bmp.Height;
            bitmapWidth = bmp.Width;
            selectedColor = ColorBgra.FromColor(Form.PaletteControl.SelectedColor);
            DoDrawLine();
            bmp.UnlockBits(bmpData);
            Form.LastModifications.UnlockBits(sourceBmpData);
            Form.BitmapZoomer.InvalidateBitmap(
                Math.Min(currX, lastMouseXY.X) - Form.PenWidth / 2 - 1,
                Math.Min(currY, lastMouseXY.Y) - Form.PenWidth / 2 - 1,
                Math.Abs(currX - lastMouseXY.X) + Form.PenWidth + 2,
                Math.Abs(currY - lastMouseXY.Y) + Form.PenWidth + 2);
        }

        public void DrawRectangle(int x1, int y1, int x2, int y2)
        {
            throw new Exception("The method or operation is not implemented.");
        }

        public void DrawFromMouse(int x1, int y1, int x2, int y2)
        {
            throw new Exception("The method or operation is not implemented.");
        }

        public void FillEllipse(int x1, int y1, int x2, int y2)
        {
            throw new Exception("The method or operation is not implemented.");
        }

        public void FillRectangle(int x1, int y1, int x2, int y2)
        {
            throw new Exception("The method or operation is not implemented.");
        }
    }
}
